package com.sfx.distributeLockStock.service.impl;

import com.sfx.distributeLockStock.dao.mapper.IMySQLLockMapper;
import com.sfx.distributeLockStock.dao.mapper.IStockMapper;
import com.sfx.distributeLockStock.dao.po.StockPO;
import com.sfx.distributeLockStock.lock.AbstractLock;
import com.sfx.distributeLockStock.lock.MySQLLock;
import com.sfx.distributeLockStock.lock.RedisLock;
import com.sfx.distributeLockStock.lock.ZookeeperLock;
import com.sfx.distributeLockStock.service.IStockService;
import jakarta.annotation.Resource;
import lombok.RequiredArgsConstructor;
import org.apache.zookeeper.ZooKeeper;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.io.IOException;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * Created with IntelliJ IDEA.
 * Description:
 * User: sfx
 * Date: 2023-11-19
 * Time: 11:31
 */
@Service
//@Scope(scopeName = "prototype", proxyMode = ScopedProxyMode.TARGET_CLASS)
//@Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE,proxyMode = ScopedProxyMode.TARGET_CLASS)
@RequiredArgsConstructor
public class StockServiceImpl implements IStockService {

    private final IStockMapper stockMapper;
    private final IMySQLLockMapper mySQLLockMapper;

    private final ReentrantLock lock = new ReentrantLock();
    private final StringRedisTemplate stringRedisTemplate;
    private final String lockName = "stock:lock:";
    private ZooKeeper zooKeeper;

    {
        try {
            zooKeeper = new ZooKeeper("127.0.0.1:2181", 10000, null);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }


    private RedissonClient redissonClient;


    @Override
    public List<StockPO> selectAll() {
        return stockMapper.selectAll();
    }

    /**
     * 1.毫无限制
     * 查询库存   如果库存充足,则更新库存
     * 如果库存不足,立即返回
     *
     * @param goodsId
     * @param count
     * @return
     */
    @Override
    public String decrementStock(Integer goodsId, Integer count) {
        return decrementStockByMySQL(goodsId, count);
    }


    /**
     * 2.加了synchronized的情况下
     * 查询库存   如果库存充足,则更新库存
     * 如果库存不足,立即返回
     *
     * @param goodsId
     * @param count
     * @return
     */
    @Override
    public synchronized String decrementstocksync(Integer goodsId, Integer count) {
        // 1. 查询库存
        Integer stockCount = stockMapper.selectStockByGoodsId(goodsId);
        if (stockCount < count) {
            return "扣减失败";
        }
        // 2. 更新库存
        Integer res = stockMapper.updateStockByGoodsId(goodsId, stockCount - count);
        return res > 0 ? "扣减成功" : "扣减失败";
    }

    /**
     * 3. 加了ReentrantLock的情况下
     * 查询库存   如果库存充足,则更新库存
     * 如果库存不足,立即返回
     *
     * @param goodsId
     * @param count
     * @return
     */
    @Override
    public String decrementStockLock(Integer goodsId, Integer count) {
        lock.tryLock();
        try {
            // 1. 查询库存
            Integer stockCount = stockMapper.selectStockByGoodsId(goodsId);
            if (stockCount < count) {
                return "扣减失败";
            }
            // 2. 更新库存
            Integer res = stockMapper.updateStockByGoodsId(goodsId, stockCount - count);
            return res > 0 ? "扣减成功" : "扣减失败";
        } finally {
            lock.unlock();
        }
    }


    /**
     * 4. 单条update(悲观锁), 判断是在sql里面进行判断
     * 查询库存   如果库存充足,则更新库存
     * 如果库存不足,立即返回
     *
     * @param goodsId
     * @param count
     * @return
     */
    @Override
    public String decrementStockUpdate(Integer goodsId, Integer count) {
        // 2. 更新库存
        Integer res = stockMapper.updateStockByGoodsIdUpdate(goodsId, count);
        return res > 0 ? "扣减成功" : "扣减失败";
    }


    /**
     * 5. for update(悲观锁)
     * 查询库存   如果库存充足,则更新库存
     * 如果库存不足,立即返回
     *
     * @param goodsId
     * @param count
     * @return
     */
    @Override
    @Transactional // 一定要加上Transactional 注解,让事务编程手动提交,如果不是手动提交,就会导致for update执行完了就提交事务了
    // for update 一定是当前事务没有提交车时候,当前客户端一直持有锁
    public String decrementStockForUpdate(Integer goodsId, Integer count) {
        // 1. 查询库存
        Integer stockCount = stockMapper.selectStockByGoodsIdForUpdate(goodsId);
        if (stockCount < count) {
            return "扣减失败";
        }
        // 2. 更新库存
        stockMapper.updateStockByGoodsId(goodsId, stockCount - count);
        return "扣减成功";
    }


    /**
     * 6. 乐观锁(使用CAS版本号机制解决库存超卖问题)
     * 查询库存   如果库存充足,则更新库存
     * 如果库存不足,立即返回
     *
     * @param goodsId
     * @param count
     * @return
     */
    @Override
    public String decrementStockCAS(Integer goodsId, Integer count) {
        Integer res = 0;
        // 没有更新成功的,继续重试
        while (res == 0) {
            // 1. 查询库存
            StockPO stockPo = stockMapper.selectStockByGoodsIdOne(goodsId);
            if (stockPo == null) {
                return "商品不存在";
            }
            Integer stockCount = stockPo.getStock();
            if (stockCount < count) {
                return "扣减失败";
            }
            // 2. 更新库存
            res = stockMapper.updateStockByGoodsIdCAS(goodsId, stockCount - count, stockPo.getVersion());
            try {
                Thread.sleep(50);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        return "扣减成功";
    }


    /**
     * 使用redis扣减库存 -- redis实现的简易版本的分布式锁
     *
     * @param goodsId 商品id
     * @param count   商品数量
     * @return 返回是否扣减库存成功
     */
    @Override
    public String decrementStockRedis(Integer goodsId, Integer count) {
        AbstractLock redisLock = null;
        try {
            redisLock = new RedisLock(stringRedisTemplate, lockName + goodsId);
            redisLock.lock();
            // 1. 检查库存数量
            int stockCount = Integer.parseInt(Objects.requireNonNull(stringRedisTemplate.opsForValue().get("stock:" + goodsId)));
            if (stockCount < count) {
                return "库存不足,扣减失败";
            }
            // 2. 库存充足则扣减库存
            stringRedisTemplate.opsForValue().decrement("stock:" + goodsId);
            return "扣减库存成功";
        } finally {
            if (redisLock != null) {
                redisLock.unlock();
            }
        }
    }

    /**
     * 使用redis 的lua脚本 扣减库存
     *
     * @param goodsId 商品id
     * @param count   商品数量
     * @return 返回是否扣减库存成功
     */
    @Override
    public String decrementStockLua(Integer goodsId, Integer count) {
        AbstractLock redisLock = null;
        try {
            redisLock = new RedisLock(stringRedisTemplate, lockName + goodsId);
            redisLock.lock(30000L);
            // 1. 检查库存数量
            int stockCount = Integer.parseInt(Objects.requireNonNull(stringRedisTemplate.opsForValue().get("stock:" + goodsId)));
            if (stockCount < count) {
                return "库存不足,扣减失败";
            }
            // 2. 库存充足则扣减库存
            stringRedisTemplate.opsForValue().decrement("stock:" + goodsId);
            return "扣减库存成功";
        } finally {
            if (redisLock != null) {
                redisLock.unlock();
            }
        }
    }


    @Override
    public String decrementStockRedissonClient(Integer goodsId, Integer count) {
        RLock lock = null;
        try {
            lock = redissonClient.getLock(lockName + goodsId);
            lock.lock();
            // 1. 检查库存数量
            int stockCount = Integer.parseInt(Objects.requireNonNull(stringRedisTemplate.opsForValue().get("stock:" + goodsId)));
            if (stockCount < count) {
                return "库存不足,扣减失败";
            }
            // 2. 库存充足则扣减库存
            stringRedisTemplate.opsForValue().decrement("stock:" + goodsId);
            return "扣减库存成功";
        } finally {
            if (lock != null) {
                lock.unlock();
            }
        }
    }

    public void lock(Lock lock) {
        lock.lock();
        lock.unlock();
    }

    public String decrementStockByZookeeper(Integer goodsId, Integer count) {
        AbstractLock lock = null;
        try {
            lock = new ZookeeperLock(zooKeeper, "stock:" + goodsId);
            lock.lock();
            lock(lock);
            // 1. 检查库存数量
            int stockCount = Integer.parseInt(Objects.requireNonNull(stringRedisTemplate.opsForValue().get("stock:" + goodsId)));
            if (stockCount < count) {
                return "库存不足,扣减失败";
            }
            // 2. 库存充足则扣减库存
            stringRedisTemplate.opsForValue().decrement("stock:" + goodsId);
            return "扣减库存成功";
        } finally {
            if (lock != null) {
                lock.unlock();
            }
        }
    }

    public String decrementStockByMySQL(Integer goodsId, Integer count) {
        AbstractLock lock = null;
        try {
            lock = new MySQLLock(mySQLLockMapper, "lock");
            lock.lock();
            // 1. 检查库存数量
            int stockCount = Integer.parseInt(Objects.requireNonNull(stringRedisTemplate.opsForValue().get("stock:" + goodsId)));
            if (stockCount < count) {
                return "库存不足,扣减失败";
            }
            // 2. 库存充足则扣减库存
            stringRedisTemplate.opsForValue().decrement("stock:" + goodsId);
            return "扣减库存成功";
        } finally {
            if (lock != null) {
                lock.unlock();
            }
        }
    }
}